//==============================================================================
//
//          DTCPServer - the tcp server class in the ofc-library
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2006-07-22 13:28:57 $ $Revision: 1.13 $

//==============================================================================

#include <stdlib.h>
#include <unistd.h>

#include "ofc/DTCPServer.h"


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DSocketAddress.h"
#include "ofc/DSocket.h"
#include "ofc/DData.h"

#define DTS_BLOCKING  (0)
#define DTS_THREADING (1)
#define DTS_FORKING   (2)

//
// The DTCPServer class implements a number of methods for implementing a TCP server.
// (Note: on WIN32 DTS_FORKING is not possible; when DTS_FORKING is used, it is switched
// to DTS_THREADING)(Note: this class assumes that the socket is blocking.)(Note: on 
// FreeBSD DTS_THREADING results in errors; when DTS_THREADING is used, it is 
// switched to DTS_FORKING)
//

@interface DTCPServer : Object
{
@private
  DSocket          *_local; // the local socket
  int               _mode;  // the running mode (DTS_THREADING, DTS_FORKING, DTS_BLOCKING)
  int               _sendFlag; // the send flag (def. 0)
  int               _recvFlag; // the receive flag (def. 0)
  unsigned          _recvLength; // the length used during receiving of requests (def. 1024)
}

#endif


@implementation DTCPServer


//// Constructors

//
// Initialise the Tcp Server
//
// @return the object
//

- (DTCPServer *) init
{
  [super init];

  _local      = [DSocket new];
  _mode       = DTS_BLOCKING;
  _sendFlag   = 0;
  _recvFlag   = 0;
  _recvLength = 1024;
  
  return self;
}

//
// Initialise the Tcp Server for a family and protocol
//
// @param family       the family (DSA_AF_INET, DSA_AF_UNIX, DSA_AF_INET6)
// @param protocol     the protocol to be used
// 
// @return the object
//

- (DTCPServer *) init :(int) family :(int) protocol
{
  [self init];
  
  [self open :family :protocol];
  
  return self;
}


//// Deconstructor

//
// Free the tcp server object
//
// @return the object
//

- free
{
  [_local free];
  
  return [super free];
}


//// Copy related methods

//
// Do a shallow copy of the object (not implemented)
//
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");
  
  return [super shallowCopy];
}


//// Member methods

//
// Return the local socket for the TCP server
// 
// @return a reference to the local socket 
//

- (DSocket *) socket
{
  return _local;
}

//
// Set the send flag
// 
// @param flag         the send flag
// 
// @return the object
// 

- (DTCPServer *) sendFlag :(int) flag
{
  _sendFlag = flag;
  
  return self;
}

//
// Return the send flag
// 
// @return the send flag
// 

- (int) sendFlag
{
  return _sendFlag;
}

//
// Set the receive flag
// 
// @param flag         the receive flag
// 
// @return the object
// 

- (DTCPServer *) recvFlag :(int) flag
{
  _recvFlag = flag;
  
  return self;
}

//
// Return the receive flag
// 
// @return the receive flag
// 

- (int) recvFlag
{
  return _recvFlag;
}

//
// Set the length for receiving of requests
// 
// @param length       the length
// 
// @return the object
// 

- (DTCPServer *) recvLength :(unsigned) length
{
  if (length > 0)
  {
    _recvLength = length;
  }
  else
  {
    WARNING(DW_INVALID_ARG, "length");
  }
  
  return self;
}

//
// Return the length for receiving of requests
// 
// @return the length
// 

- (unsigned) recvLength
{
  return _recvLength;
}


//// Main methods

//
// Open the TCP server
// 
// @param family       the family (DSA_AF_INET, DSA_AF_UNIX, DSA_AF_INET6)
// @param protocol     the protocol to be used
// 
// @return success
// 

- (BOOL) open :(int) family :(int) protocol
{
  BOOL ok = NO;

  if ([_local open :family :DSK_STREAM :protocol])
  {
    [_local reuseAddr :YES];
    
    ok = YES;
  }
  
  return ok;
}
      
//
// Wait for connections and call 'handleConnection' on connection. Note: DTS_FORKING does not work
// on WIN32; it is converted to DTS_THREADING.
// 
// @param address      the address for the server
// @param backlog      the number of pending connnections before refuse
// @param mode         the running mode (DTS_THREADING, DTS_FORKING, DTS_BLOCKING)
// @param connections  the number of connections to be made before returning (or -1 for unlimited)
// 
// @return success
// 

- (BOOL) start :(id <DSocketAddressable>) address :(int) backlog :(int) mode :(int) connections
{
  BOOL ok = NO;

  if ((mode < DTS_BLOCKING) && (mode > DTS_FORKING))
  {
    WARNING(DW_INVALID_ARG, "mode");
  }
  else
  {
#ifdef WIN32
    if (mode == DTS_FORKING)
    {
      mode = DTS_THREADING;
    }
#endif
#ifdef __FreeBSD__
    if (mode == DTS_THREADING)
    {
      mode = DTS_FORKING;
    }
#endif
    if ([_local bind :address])
    {
      if ([_local listen :backlog])
      {
        _mode = mode;
        
        ok = YES;
        while ((ok) && ((connections == -1) || (connections > 0)))
        {
          DSocket *peer = nil;

          peer = [_local accept];

          ok = (peer != nil);
          
          if (ok)
          {
            switch(_mode)
            {
             case DTS_THREADING:
              {
                ok = (objc_thread_detach(@selector(handleConnection:), self, peer) != NULL);
                
                if (!ok)
                {
                  WARNING(DW_UNEXPECTED_ERROR, "thread failed");
                }
              }
              break;
#ifndef WIN32                
             case DTS_FORKING:
              {
                int child = fork();
              
                if (child < 0)
                {
                  WARNING(DW_UNEXPECTED_ERROR, "fork failed");
                }
                else if (child == 0) // child
                {
                  [self handleConnection :peer];
                }
              }
              break;
#endif                
             default:
              {
                [self handleConnection :peer];
              }
              break;
            }
          
            if (connections > 0)
              connections--;
          }
        }
      }
    }
    
  }
  
  return ok;
}

//
// Handle a new connection with a peer (called by start)
//
// @param peer         the connected socket
// 
// @return the object
//

- (void) handleConnection :(DSocket *) peer
{
  DData *recvMsg     = nil;
  DData *sendMsg     = [DData new];
  BOOL   ok          = YES;
  BOOL   stopRequest = NO;
  
  do
  {
    recvMsg = [peer recv :_recvLength :_recvFlag];
    
    if (recvMsg != nil)
    {
      [sendMsg clear];
      
      stopRequest = [self handleRequest :recvMsg :sendMsg];
      
      if ([sendMsg length] > 0)
      {
        ok = ([peer send :(void *)[sendMsg data] :[sendMsg length] :_sendFlag] >= 0);
      }
      
      [recvMsg free]; recvMsg = nil;
    }
    else
    {
      ok = NO;
    }
  }
  while ((!stopRequest) && (ok));

  // Close the connection
  [peer    free];
  [sendMsg free];
  
  switch(_mode)
  {
   case DTS_THREADING:
    objc_thread_exit();
    break;
    
   case DTS_FORKING:
    exit(0);
    break;
    
   default:
    break;
  }

}

//
// Handle a new request by the client (called by handleConnection, to be overridden)
// 
// @param request      the request made by the client
// @param response     the destination for the response
// 
// @return should the connection be stopped ?
//

- (BOOL) handleRequest :(DData *) request :(DData *) response
{
  return YES; 
}

@end

/*===========================================================================*/

